目录5

Python项目管理

虚拟环境

使用 pip 安装库,会被放到一个全局的环境中,被电脑上所有的 Python 项目共享,这种共享机制会带来如下两个问题

  1. 版本冲突:比如新项目需要 Flask 3.11,但电脑上某个旧项目可能只兼容 3.0 版
  2. 依赖地狱:即复杂的依赖关系,一个库通常依赖其它几个库,而这些库又有自己各自的依赖,这样层层嵌套,就会引发更多的版本冲突

虚拟环境就是为了解决这个问题被设计出来的。它可以为每个项目创建一个独立的 Python 工作空间,可以使用如下命令产生一个虚拟环境。

Cmd1 行
python -m venv .venv

这里 .venv 就是给虚拟环境起的名字(可任取,但推荐这样),执行后项目里会产生一个名为 .venv 的目录,存放着一个独立的 Python 环境所需要的所有文件

如果没有使用 IDE (如PyCharm、VS Code、Jupyter Notebook 等) ,还需使用如下命令激活虚拟环境

Cmd1 行
source .venv/bin/activate

激活虚拟环境之后,再使用 pip 安装之前的库,这一次 Flask 就会被安装到虚拟文件夹里了。这样,每个项目都可以拥有自己专属的虚拟文件夹,存放自己专属的依赖库,也就不会在产生冲突了。

虚拟环境起作用的原理,主要就是修改了 Python 中 sys. path 这个变量,

Cmd2 行
import sys,pprint
pprint.pp(sys.path)

sys. path 是一个列表,记录了 Python 在导入模块时需要搜索的一系列文件夹路径,在执行 import 时,Python 会严格按照这个列表的顺序从头到尾检查这些路径,直到找到相应的包,在激活了虚拟环境之后,sys. path 会被修改,虚拟目录会被添加到这个搜索列表里.

隔离了项目之间的依赖,仍然有下面的问题:如何把项目的依赖列表方便准确地分享给别人? 一行一行执行一大堆 pip install 命令 使用 pip freeze 命令

Cmd1 行
pip freeze

打印出当前虚拟环境中所有已经安装好的包,以及确切的版本号,用重定向符

Cmd1 行
pip freeze > requirements.txt 

输出到 requirements. txt (可任取,但推荐这样),这样对方只需要执行

Cmd1 行
pip install -r requirements.txt 

pip 就会读取 requirements. txt 文件,并把里面的所有依赖全部安装一遍。

优点:直观 缺陷: pip freeze 分不清什么是我们项目真正需要的直接依赖,什么是这些依赖而引入的间接依赖。并且 pip 在卸载时也分不清这种依赖关系,比如当使用 pip uninstall 卸载 Flask时,pip 只会移除 Flask 本身,而那些因为他而被装上来的间接依赖全都留在了环境里面。

现代 Python 项目的标准解决方案是使用 pyproject. toml ,这是官方指定的统一配置文件,在此之前,不同的开发工具通常有各自独立的配置文件,比如

  • 类型检查器 mypy 会使用 mypy. ini
  • 测试框架 pytest 则使用 pytest. ini 当项目中使用的工具增多时,根目录下就会出现大量零散的配置文件,如今 Python 生态中绝大多数工具都支持了 pyproject. toml 文件,这样开发者就只需要维护这一个配置文件。

如果想用 pyproject. toml 替换 requirements ,只需要在 pyproject. toml 文件里添加一个 dependencies 的列表,关键区别在于声明项目直接依赖的包就可以了。比如

[project]
name = "proj"
version = "0.1.0"
dependencies = {
    "Flask==3.1.1"
}

然后 requirements 文件就可以被删掉了,若未来需要删除 Flask 3.1.1 依赖,只需要在 pyproject. toml 中删除对应行即可,依赖写好之后,在根目录下执行

Cmd1 行
pip install .

安装依赖,这里的‘.’表示当前目录,这条命令会做两件事:

  1. 将当前项目打包为一个标准的 Python 软件包
  2. 安装刚刚打包好的软件包,并自动把所有声明的依赖一并安装进来

但是也带来新问题: 检查虚拟环境中 site-packages 目录时,会发现自己写的. py 文件也会被安装进去,这样项目里面就有了两份一模一样的源代码,在开发阶段很麻烦:

如果我们修改了源代码,这个改动并不会自动同步到虚拟环境中,为解决这个问题,只需在安装时加上一个 -e 参数,

Cmd1 行
pip install -e .

这样 pip 安装时就不会把源代码复制到 site-packages 目录了,它会在这个目录下创建一个连接文件,当 python 要 import 项目代码时,他会顺着链接直接找到我们正在编辑的那个文件,确保我们的所有修改能够立刻生效。

以上纯手工方案很麻烦,社区中有 Poetry, UV, PDM 等解决方案,可以理解为是对 venv, pip 的高级封装。使用 uv 只需要一条简单的命令

Cmd1 行
uv add flask

就能完成所有的事情:

  • 自动修改 pyproject. toml 文件:把 flask 添加到 dependencies 列表里面
  • 检查并自动创建 .venv 虚拟环境
  • 把这个包和他所有的间接依赖安装到虚拟环境中 如果是项目协作者,只需要执行
Cmd1 行
uv sync 

uv 就会自动读取 pyproject. toml 文件,搭建好虚拟环境,并安装好所有的依赖,使用 uv 管理项目后,依然可以用传统命令运行代码,使用 source 命令激活虚拟环境,然后运行 python 文件;

Cmd1 行
python main.py

同时 uv 提供了更加直接的做法

Cmd1 行
uv run main.py

作用是在虚拟环境的上下文中执行命令,不需要手动激活环境,uv 会自动找到对应的 venv 在其中执行命名,然后再退出来。

使用 uv 管理

安装 uv

使用 uv 可以完成 Python 安装-> 虚拟环境 -> 依赖管理 -> 工具安装 -> 打包发布完整流程

使用如下命令安装 uv 并添加到系统路径 Windows

Cmd1 行
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"

Python 项目管理

macOS and Linux

Cmd1 行
curl -LsSf https://astral.sh/uv/install.sh | sh

创建虚拟环境

Cmd1 行
uv venv

在添加库时会自动创建虚拟环境

python 版本管理:

打印出 uv 支持的所有 Python 版本

Cmd1 行
uv python list 

安装特定版本的 Python

Cmd1 行
uv python install 想要安装的版本

使用特定版本的 Python 临时运行一个文件

Cmd1 行
uv run -p 版本号 脚本名称

如果想要在交互的页面中使用

Cmd1 行
uv run -p 版本号 python

其实在使用 uv run 之前并不需要特意的执行 uv python install ,在使用指定 python 版本时,如 uv run -p 版本号 python,如果之前没有安装过该版本 python,uv 就会在这时开始安装。

要使用jupyter需要添加ipykernel

项目依赖问题

创建 uv 工程

Cmd1 行
uv init -p python版本号

此时 uv 会创建一系列的文件,

  • hello. py : uv 生成的示例代码,没有什么用,可以删除
  • .python-version: 记录了 python 的版本信息,如果想要换版本,可以在这里直接修改
  • main. py: 这是之前我们写好的源代码,在 IDE 也许会提示它找不到~库,这时可以使用如下命令把这个库安装到当前工程中 添加库依赖
Cmd1 行
uv add 库的名称

或者从 requirements. txt 转化

Cmd1 行
uv add -r requirements.txt

这时 uv 不仅会下载库文件,还会自动为工程创建一个虚拟环境;可以点击 vscode 中 python 版本号来调整 vscode 使用的 python 解释器,使用我们的虚拟环境。我们所安装的工程依赖会被记录在 pyproject. toml 文件中

如果想知道工程所有库之间的依赖关系,可以使用如下命令打印出整个依赖树

Cmd1 行
uv tree 

现在想要使用 ruff 工具检查代码是否符合规范,检查是否安装

Cmd1 行
ruff check 

方法一:当作依赖直接引用进来

Cmd1 行
uv add ruff --dev

加入 dev 参数是为了避免打包时把 ruff 也一起打包进来;但本质上它是一个工具,和代码是不相关的,而–dev 依赖应该还是放的与代码有关的,比如单元测试用到的 pytest 或者 mock 等 方法二:把想要安装的工具直接安装到系统之中。

Cmd1 行
uv tool install ruff

此时的 ruff 就是脱离当前工程独立运行的了。

Cmd1 行
which ruff

可以看到路径非当前虚拟环境。 使用 uv tool install 安装,整个系统都可以用,并且会为每个工具建立自己的虚拟环境,所以也不必担心库之间的冲突。 查看当前已经安装的工具

Cmd1 行
uv tool list 

程序开发完成后,希望把它打包成一个可以直接运行的脚本,在 pyproject. toml 文件中,加入

[project.scripts]
脚本名称 = '想要运行的python脚本:函数名'

比如 ai. py 中有 main ()

[project.scripts]
脚本名称 = 'ai:main'

然后使用如下命令把整个工程打包为 whl 文件

Cmd1 行
uv build

其它人就可以安装(用上面介绍的方法)和使用该脚本了

Cmd1 行
uv tool install 文件.whl 

现在就可以直接使用 ai 命令了